package com.easy.framework.database.utils;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.easy.framework.common.model.BaseQuery;
import com.easy.framework.common.model.OrderBy;
import com.easy.framework.common.utils.Func;
import com.easy.framework.database.annotation.Query;
import com.easy.framework.database.annotation.QueryType;
import org.springframework.util.StringUtils;

import java.io.Serializable;
import java.lang.invoke.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Consumer;

/**
 * 查询注解工具
 *
 * @author Yang Huijing
 */
public class QueryUtil {


    public static <T,Q extends BaseQuery> LambdaQueryWrapper<T> lambdaQuery(Q q, Class<T> clazz) {
        LambdaQueryWrapper<T> wrapper = null;
        if (q != null) {
            wrapper = Wrappers.<T>lambdaQuery();
            Map<String, List<Field>> fieldMap = QueryUtil.getQueryFieldMap(q);
            for (String key : fieldMap.keySet()) {
                List<Field> fieldList = fieldMap.get(key);
                LambdaQueryWrapper<T> lambdaQueryWrapper = Wrappers.<T>lambdaQuery();
                for (Field field : fieldList) {
                    Query query = field.getAnnotation(Query.class);
//                    Condition condition = Arrays.stream(query.condition()).filter(c -> c.groupName().equals(key)).findFirst().orElse(null);
//                    if (Func.isEmpty(condition)) {
//                        continue;
//                    }
                    // 属性名称
                    String propertyName = Func.isNotEmpty(query.name()) ? query.name() : field.getName();
                    Object fieldValue = null;
                    try {
                        fieldValue = field.get(q);
                    } catch (Exception e) {
                    }
                    if (Func.isEmpty(fieldValue)) {
                        continue;
                    }
                    SFunction<T, ?> sfunction = QueryUtil.getter(propertyName, clazz);
                    if (QueryType.NE.equals(query.type())) {
                        lambdaQueryWrapper.ne(sfunction, fieldValue);
                    } else if (QueryType.GE.equals(query.type())) {
                        lambdaQueryWrapper.ge(sfunction, fieldValue);
                    } else if (QueryType.GT.equals(query.type())) {
                        lambdaQueryWrapper.gt(sfunction, fieldValue);
                    } else if (QueryType.LE.equals(query.type())) {
                        lambdaQueryWrapper.le(sfunction, fieldValue);
                    } else if (QueryType.LT.equals(query.type())) {
                        lambdaQueryWrapper.lt(sfunction, fieldValue);
                    } else if (QueryType.LIKE.equals(query.type())) {
                        lambdaQueryWrapper.like(sfunction, fieldValue);
                    } else if (QueryType.LIKE_LEFT.equals(query.type())) {
                        lambdaQueryWrapper.likeLeft(sfunction, fieldValue);
                    } else if (QueryType.LIKE_RIGHT.equals(query.type())) {
                        lambdaQueryWrapper.likeRight(sfunction, fieldValue);
                    } else if (QueryType.IN.equals(query.type())) {
                        if (field.getType().isArray()) {
                            Object[] objects = (Object[]) fieldValue;
                            if (objects.length == 0) {
                                continue;
                            }
                            lambdaQueryWrapper.in(sfunction, objects);
                        } else {
                            @SuppressWarnings("unchecked")
                            Collection<Object> objects = (Collection<Object>) fieldValue;
                            if (objects.size() == 0) {
                                continue;
                            }
                            lambdaQueryWrapper.in(sfunction, objects);
                        }
                    } else if (QueryType.NOT_IN.equals(query.type())) {
                        if (field.getType().isArray()) {
                            Object[] objects = (Object[]) fieldValue;
                            if (objects.length == 0) {
                                continue;
                            }
                            lambdaQueryWrapper.notIn(sfunction, objects);
                        } else {
                            @SuppressWarnings("unchecked")
                            Collection<Object> objects = (Collection<Object>) fieldValue;
                            if (objects.size() == 0) {
                                continue;
                            }
                            lambdaQueryWrapper.notIn(sfunction, objects);
                        }
                    } else {
                        lambdaQueryWrapper.eq(sfunction, fieldValue);
                    }
                }
                if (!lambdaQueryWrapper.isEmptyOfWhere()) {
                    if (fieldMap.size() > 1) {
                        Consumer<LambdaQueryWrapper<T>> consumer = w -> {
                        };
                        consumer.accept(lambdaQueryWrapper);
                        wrapper.or(consumer);
                    } else {
                        wrapper = lambdaQueryWrapper;
                    }
                }
            }
            if(Func.isNotEmpty(q.getOrderBy()) && Func.isNotEmpty(q.getOrderBy().getProp())){
                if(OrderBy.ASC.equals(q.getOrderBy().getOrder())){
                    wrapper.orderByAsc(QueryUtil.getter(q.getOrderBy().getProp(), clazz));
                }else{
                    wrapper.orderByDesc(QueryUtil.getter(q.getOrderBy().getProp(), clazz));
                }
            }
        }
        return wrapper;
    }

    public static <T,Q extends BaseQuery> Optional<LambdaQueryWrapper<T>> lambdaQueryOptional(Q q, Class<T> clazz) {
        return Optional.ofNullable(lambdaQuery(q,clazz));
    }

    @SuppressWarnings("unchecked")
    public static <T,R> SFunction<T, R> getter(String propertyName, Class<T> clazz) {
        try { // 构造getter方法名
            String getterName = "get" + StringUtils.capitalize(propertyName);
            Method method = clazz.getDeclaredMethod(getterName);
            method.setAccessible(true);
            // 使用 MethodHandles 创建 Lambda
            MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(clazz, MethodHandles.lookup());
            MethodHandle handle = lookup.unreflect(method);
            CallSite site = LambdaMetafactory.altMetafactory(
                    lookup,
                    "apply",
                    MethodType.methodType(SFunction.class),
                    MethodType.methodType(Object.class, Object.class), // 接口方法类型
                    handle,                                             // 实际方法句柄
                    MethodType.methodType(method.getReturnType(), clazz), // 实际方法类型
                    LambdaMetafactory.FLAG_SERIALIZABLE
            );
            return (SFunction<T, R>) site.getTarget().invokeExact();
        } catch (Exception e) {
            throw new RuntimeException(e);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }


    public static <T> Map<String, List<Field>> getQueryFieldMap(T q) {
        Map<String, List<Field>> map = new HashMap<>();
        List<Field> fieldList = new ArrayList<>();
        if (q == null) {
            return map;
        }
        Class<?> currentClass = q.getClass();
        while (currentClass.getSuperclass() != null
                && !currentClass.equals(BaseQuery.class)) {
            Field[] fields = currentClass.getDeclaredFields();
            fieldList.addAll(List.of(fields));
            currentClass = currentClass.getSuperclass();
        }

        for (int i = 0; i < fieldList.size(); i++) {
            Field field = fieldList.get(i);
            Query query = field.getAnnotation(Query.class);
            if (query == null) {
                fieldList.remove(i--);
                continue;
            }
            field.setAccessible(true);
//            for (Condition condition : query.condition()) {
                if (!map.containsKey(query.groupName())) {
                    map.put(query.groupName(), new ArrayList<Field>());
                }
                map.get(query.groupName()).add(field);
//            }
        }
        return map;
    }
}
